package com.googlecode.dgwt.client.dijit.form.base;

import com.google.gwt.core.client.GWT;
import com.google.gwt.dom.client.Element;
import com.google.gwt.dom.client.InputElement;
import com.google.gwt.event.dom.client.ChangeEvent;
import com.google.gwt.event.dom.client.ChangeHandler;
import com.google.gwt.event.dom.client.HasChangeHandlers;
import com.google.gwt.event.logical.shared.ValueChangeEvent;
import com.google.gwt.event.logical.shared.ValueChangeHandler;
import com.google.gwt.event.shared.HandlerRegistration;
import com.google.gwt.user.client.DOM;
import com.google.gwt.user.client.Event;
import com.google.gwt.user.client.ui.HasName;
import com.google.gwt.user.client.ui.HasText;
import com.google.gwt.user.client.ui.HasValue;
import com.google.gwt.user.client.ui.impl.TextBoxImpl;
import com.googlecode.dgwt.client.dijit.base.HasTextAlignment;
import com.googlecode.dgwt.client.dijit.base.state.FocusedState;
import com.googlecode.dgwt.client.dijit.base.state.ReadOnlyState;
import com.googlecode.dgwt.client.dijit.event.ChangingEvent;
import com.googlecode.dgwt.client.dijit.event.ChangingHandler;
import com.googlecode.dgwt.client.dojo.Dojo;
import com.googlecode.dgwt.client.dojo.string.RegExp;

/**
 * @author wellsantos@wellsantos.com
 * @since 0.01
 */
public class TextBoxBase extends FormValueWidget implements HasChangeHandlers,
                                                            HasTextAlignment, 
                                                            HasValue<String>, 
                                                            HasText, 
                                                            HasName {
    
    
    protected static final TextBoxImpl impl = GWT.create(TextBoxImpl.class);

    
    protected String lastStoredValue;
    protected String lastChangingValue;
    
    private RegExp validation;
    private boolean doingChanging;
    private Event currentEvent;

    
    protected TextBoxBase(boolean ready) {
        super(ready);
        requireEventState(FocusedState.get());
    }

    
    public TextBoxBase(Element element) {
        this(false);
        setDomElement(element);
        postCreate();
    }


    public InputElement getInputElement() {
        return getAttachedElement("inputElement").cast();
    }

    public int getMaxLength() {
        return getInputElement().getMaxLength();
    }

    public int getVisibleLength() {
        return getInputElement().getSize();
    }

    public void setMaxLength(int length) {
        getInputElement().setMaxLength(length);
    }

    public void setVisibleLength(int length) {
        getInputElement().setSize(length);
    }

    
    @Override
    protected void initialize() {
        super.initialize();
        setElementForAttribute("size", "inputElement");
        setElementForAttribute("maxlength", "inputElement");
        setElementForAttribute("textalign", "inputElement");
    }


    @Override
    protected void onPostCreate() {
        super.onPostCreate();
        lastStoredValue = getValue();
        lastChangingValue = lastStoredValue;
        bindChangingHandler();
        sinkEvents(Event.ONBLUR
                 | Event.ONKEYUP
                 | Event.ONCHANGE 
                 | Event.ONMOUSEOUT);
    }


    @Override
    public String getTemplateParameter(String name) {
        if ("type".equals(name)){
            return "text";
        }
        if ("autocomplete".equals(name)){
            return "on";
        }
        return super.getTemplateParameter(name);
    }
    

    public HandlerRegistration addChangeHandler(ChangeHandler handler) {
        return addDomHandler(handler, ChangeEvent.getType());
    }

    
    public HandlerRegistration addValueChangeHandler(ValueChangeHandler<String> handler) {
        return addHandler(handler, ValueChangeEvent.getType());
    }
    
    
    public HandlerRegistration addChangingHandler(ChangingHandler<String> handler) {
        return addHandler(handler, ChangingEvent.getType());
    }
    
    
    /*
     * TODO : criar uma implementação independente para ChangingHandler
     */
    private void bindChangingHandler() {
        if (Dojo.navigator.isGecko()) {
            bindChangingHandlerGecko(getInputElement());
        }
        else if (Dojo.navigator.isIE()) {
            bindChangingHandlerIE(getInputElement());
        }        
    }


    private native void bindChangingHandlerIE(InputElement input) /*-{
        var self=this;
        input.onpaste = function(){
            self.@com.googlecode.dgwt.client.dijit.form.base.TextBoxBase::doChanging()();
        };
    }-*/;


    private native void bindChangingHandlerGecko(InputElement input) /*-{
        var self=this;
        input.addEventListener("input", function(){
            self.@com.googlecode.dgwt.client.dijit.form.base.TextBoxBase::doChanging()();
        }, false);
    }-*/;


    private void doChanging() {
        this.getValue();
    }
    
    
    @Override
    public String getValue() {
        String value = super.getValue();
        if (!doingChanging) {
            doingChanging = true;
            try {
                if (!value.equals(lastChangingValue)) {
                    onChanging(lastChangingValue);
                    ChangingEvent.fire(this); 
                    lastChangingValue = value = super.getValue();
                }
            } finally {
                doingChanging = false;
            }
        }
        return value;
    }


    private void doChange() {
        String val = format(getText());
        
        if (!val.equals(getValue())) {
            int pos = getCursorPos();
            int len = getSelectionLength();
            setValue(val, false);
            setSelectionRange(pos, len);
        }
        
        ValueChangeEvent.fire(TextBoxBase.this, val);
        lastStoredValue = getValue();
    }

    
    protected void onChanging(String lastValue) {        
    }
    
    
    /**
     * If a keyboard event is currently being handled on this text box, calling
     * this method will suppress it. This allows listeners to easily filter
     * keyboard input.
     */
    public void cancelKey() { // FIXME cancelKey() é desnecessário !!
        if (currentEvent != null) {
            DOM.eventPreventDefault(currentEvent);
        }
    }

    /**
     * Gets the current position of the cursor (this also serves as the beginning
     * of the text selection).
     * 
     * @return the cursor's position
     */
    public int getCursorPos() {
        return impl.getCursorPos(getElement());
    }

    /**
     * Gets the text currently selected within this text box.
     * 
     * @return the selected text, or an empty string if none is selected
     */
    public String getSelectedText() {
        int start = getCursorPos();
        if (start < 0) {
            return "";
        }
        int length = getSelectionLength();
        return getValue().substring(start, start + length);
    }

    /**
     * Gets the length of the current text selection.
     * 
     * @return the text selection length
     */
    public int getSelectionLength() {
        return impl.getSelectionLength(getElement());
    }

    /**
     * Determines whether or not the widget is read-only.
     * 
     * @return <code>true</code> if the widget is currently read-only,
     *         <code>false</code> if the widget is currently editable
     */
    public boolean isReadOnly() {
        return getInputElement().isReadOnly();
    }

    
    @Override
    public void onBrowserEvent(Event event) {
        int type = DOM.eventGetType(event);
        switch (type) {
            case Event.ONBLUR:
            case Event.ONKEYUP:
            case Event.ONMOUSEOUT: {
                doChanging();
                break;
            }
            case Event.ONCHANGE: {
                doChange();
                break;
            }
        }
        if ((type & Event.KEYEVENTS) != 0) {
            // Fire the keyboard event. Hang on to the current event object so that
            // cancelKey() and setKey() can be implemented.
            currentEvent = event;
            // Call the superclass' onBrowserEvent as that includes the key event
            // handlers.
            super.onBrowserEvent(event);
            currentEvent = null;
        } else {
            // Handles Focus and Click events.            
            super.onBrowserEvent(event);
        }
    }

    
    /**
     * Selects all of the text in the box.
     * 
     * This will only work when the widget is attached to the document and not
     * hidden.
     */
    public void selectAll() {
        setSelectionRange(0, Integer.MAX_VALUE);
    }

    /**
     * Sets the cursor position.
     * 
     * This will only work when the widget is attached to the document and not
     * hidden.
     * 
     * @param pos the new cursor position
     */
    public void setCursorPos(int pos) {
        setSelectionRange(pos, 0);
    }

    /**
     * Turns read-only mode on or off.
     * 
     * @param readOnly if <code>true</code>, the widget becomes read-only; if
     *          <code>false</code> the widget becomes editable
     */
    public void setReadOnly(boolean readOnly) {
        getInputElement().setReadOnly(readOnly);
        ReadOnlyState.get().update(this, readOnly);
    }

    /**
     * Sets the range of text to be selected.
     * 
     * This will only work when the widget is attached to the document and not
     * hidden.
     * 
     * @param pos the position of the first character to be selected
     * @param length the number of characters to be selected
     */
    public void setSelectionRange(int pos, int length) {
        // Setting the selection range will not work for unattached elements.
        if (!isAttached()) {
            return;
        }

        int curLen = getValue().length();
        
        if (length < 0) { length = 0; }        
        if (pos < 0) { pos = 0; }
        
        if (pos > curLen) { pos = curLen; }
        if (pos + length > curLen) { length = curLen - pos; }
        
        impl.setSelectionRange(getElement(), pos, length);
    }

    /**
     * Sets the alignment of the text in the text box.
     * 
     * @param align the text alignment (as specified by {@link #ALIGN_CENTER},
     *          {@link #ALIGN_JUSTIFY}, {@link #ALIGN_LEFT}, and
     *          {@link #ALIGN_RIGHT})
     */
    public void setTextAlignment(TextAlignmentConstant align) {
        getInputElement().setPropertyString("textAlign", align.getTextAlignString());
    }

    
    @Override
    public void setValue(String value) {
        setValue(value, false);
    }


    @Override
    public void setValue(String value, boolean fireEvents) {
        if (fireEvents) {
            String oldValue = getValue();
            super.setValue(value);
            ValueChangeEvent.fireIfNotEqual(this, oldValue, value);
        } else {
            super.setValue(value);
        }
    }

    
    public String getLastModifiedValue() {
        return lastStoredValue;
    }
    
    
    public String getLastModifiedText() {
        return unformat(lastStoredValue);
    }
    
    
    public boolean isModified() {
        return !getLastModifiedText().equals(getText());
    }

    @Override
    protected String doValidation() {
        String message = super.doValidation();

        if (getValidation() != null) {
            if (!getValidation().test(getText())) {
                message = getInvalidMessage();
            }
        }
        return message;
    }


    public RegExp getValidation() {
        return validation;
    }

    public void setValidation(RegExp validation) {
        this.validation = validation;
    }

}
